#version 410

//#define DEBUG
#define RBGA_DEPTH

out vec4 out_Color;

uniform sampler2D u_depthTX;
uniform sampler2D u_normalTX;
uniform sampler2D u_diffuseTX;
uniform sampler2D u_miscTX;

uniform mat4 u_invPersp;
uniform mat3 u_viewInvTrans;
uniform mat4 u_View;

uniform vec3 u_lightColor;
uniform vec3 u_lightDirection;
uniform vec3 u_lightPosition;
uniform float u_halfAngleCos;
uniform float u_cutoffFar;
uniform float u_fadeFar;

in vec2 fs_Texcoord;

const float spotFactor = .6f*.8f;

// Code from : http://aras-p.info/texts/CompactNormalStorage.html
vec3 unpackNormal(in vec2 encNorm) {
	encNorm = encNorm*4.0f - 2.0f;
	float f = dot(encNorm, encNorm);
	float g = sqrt(1.0f -.25f*f);
	return vec3(encNorm*g,1.0f - .5f*f);
}

float linearizeDepth(float exp_depth, float near, float far) {
    return	(2 * near) / (far + near -  exp_depth * (far - near)); 
}

vec4 packFloatTo4x8(in float val) {
	uint a = floatBitsToInt(val);
	return vec4((bitfieldExtract(a,0,8))/256.0f,
				(bitfieldExtract(a,8,8))/256.0f,
				(bitfieldExtract(a,16,8))/256.0f,
				(bitfieldExtract(a,24,8))/256.0f);
}

float unpack4x8ToFloat(in vec4 val) {
	uint a = uint(val.x*256.0f+.5f) + 
	uint(val.y*256.0f+.5f)*256u + 
	uint(val.z*256.0f+.5f)*256u*256u+
	uint(val.w*256.0f+.5f)*256u*256u*256u;
	return uintBitsToFloat(a);
}

// Packing and unpacking code courtesy of:
// http://www.ozone3d.net/blogs/lab/20080604/glsl-float-to-rgba8-encoder/
// http://olivers.posterous.com/linear-depth-in-glsl-for-real
vec4 pack2FloatTo4x8(in vec2 val) {
	const vec2 bitSh = vec2(256.0f, 1.0f);
	const vec2 bitMsk = vec2(0.0f, 1.0f/256.0f);
	vec2 res1 = fract(val.x * bitSh);
	res1 -= res1.xx * bitMsk;
	vec2 res2 = fract(val.y * bitSh);
	res2 -= res2.xx * bitMsk;
	return vec4(res1.x,res1.y,res2.x,res2.y);
}

vec2 unpack4x8To2Float(in vec4 val) {
	const vec2 unshift = vec2(1.0f/256.0f, 1.0f);
	return vec2(dot(val.xy, unshift), dot(val.zw, unshift));
}

vec3 getVSPosition() {
#ifdef DEBUG
	if(texture(u_depthTX, fs_Texcoord).x == 1.0f)
		return vec3(.0f, .0f, 100000.0f);
#endif

	// Go from texture coordinate + depth to view space
#ifdef RBGA_DEPTH
	vec4 sPos = vec4(2.0f*fs_Texcoord.x - 1.0f, fs_Texcoord.y * 2.0f - 1.0f, 2.0*unpack4x8ToFloat(texture(u_depthTX, fs_Texcoord)) - 1.0f, 1.0f);
#else
	vec4 sPos = vec4(2.0f*fs_Texcoord.x - 1.0f, fs_Texcoord.y * 2.0f - 1.0f, 2.0*texture(u_depthTX, fs_Texcoord).x - 1.0f, 1.0f);
#endif
	sPos = u_invPersp * sPos;
	return sPos.xyz/sPos.w;
}

vec3 lightCalc() {
	
	// Change position to view space
	vec3 lpos = (u_View * vec4(u_lightPosition, 1.0f)).xyz;	
	
	vec3 vsPos = getVSPosition();
	
	// Change direction to view space
	vec3 ldir = normalize(u_viewInvTrans*u_lightDirection);
	vec3 l2sDir = normalize(vsPos - lpos);
	
	float dirDirDot = dot(l2sDir, ldir);

	// Compare direction from point to light with direction of light
	if(dirDirDot > u_halfAngleCos) {
	
		float dist = distance(lpos, vsPos);
	
		if(dist < u_cutoffFar) {	
			float attenuate = 1.0f;
			
			
			if(dist > u_fadeFar) {
				attenuate = 1.0f - (dist - u_fadeFar)/(u_cutoffFar - u_fadeFar);
			}

			return u_lightColor*(max(dot(-(l2sDir), normalize(unpackNormal(unpack4x8To2Float(texture(u_normalTX, fs_Texcoord))))), 0))*attenuate*pow((dirDirDot-u_halfAngleCos)/(1 - u_halfAngleCos),2.0f);			
			
		}
		
	}
	
	return vec3(.0f, .0f, .0f);
}

void main(void) {
	out_Color = vec4(texture(u_diffuseTX, fs_Texcoord).xyz*lightCalc()*spotFactor, 1.0f);// + vec4(.0f, .4f, .0f, .0f);
}